#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/sched.h>

#include<linux/semaphore.h>
#include<linux/mutex.h>

#include "gtest.h"

#define LOCK_USE 1 //0:semaphore,1:mutex

static bool enable_tracer=false;
static bool enable_fsbc=true;


module_param(enable_tracer,bool,S_IRUGO);
module_param(enable_fsbc,bool,S_IRUGO);

struct g_char_dev{		
	struct cdev cdev;
	char *c;
	int n;
	struct semaphore sema;
	struct mutex mtx;
};

int g_open(struct inode *inode, struct file *filp)
{
	struct g_char_dev *g_dev;
	printk(KERN_INFO "%s open \n",current->comm);
	g_dev = container_of(inode->i_cdev,struct g_char_dev,cdev);  
	filp->private_data = g_dev;		

	return 0;
}

ssize_t g_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
	ssize_t retval=0;
	struct g_char_dev *g_dev=filp->private_data;
	//printk(KERN_INFO "read hc_dev %p\n",hc_dev);
	
	if(*f_pos >= g_dev->n)
		goto out;
	if(*f_pos + count > g_dev->n)
		count = g_dev->n - *f_pos;
	
	if(copy_to_user(buf,g_dev->c,count))
	{
		retval = -EFAULT;
		goto out;
	}

	*f_pos += count;
	return count;	
out:
	return retval;
}

ssize_t g_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
	struct g_char_dev *g_dev=filp->private_data;
	int retval = -ENOMEM;
	#if (LOCK_USE==0)
	if(down_interruptible(&g_dev->sema))		//-EINTR
		return -ERESTARTSYS;
	#endif
	#if (LOCK_USE==1)
	if(mutex_lock_interruptible(&g_dev->mtx))   //-EINTR
		return -ERESTARTSYS;
	#endif

	kfree(g_dev->c);
	g_dev->c=NULL;
	g_dev->n=0;
	g_dev->c = kzalloc(count,GFP_KERNEL);
	if(!g_dev->c)
		goto out;
	if(copy_from_user(g_dev->c,buf,count))
	{
		retval = -EFAULT;
		goto fail_copy;
	}
	g_dev->n = count;
	
	#if (LOCK_USE==0)
	up(&g_dev->sema);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&g_dev->mtx);
	#endif
	return count;	 
fail_copy:
	kfree(g_dev->c);
out:
	#if (LOCK_USE==0)
	up(&g_dev->sema);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&g_dev->mtx);
	#endif
	return retval;					//不能返回0，否则会不停的写
}
int g_release(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "%s release\n",current->comm);
	return 0;
}

long g_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct g_char_dev *g_dev = filp->private_data;
	long retval = 0;
	int tmp,err=0;
	
	if (_IOC_TYPE(cmd) != G_IOC_MAGIC) return -ENOTTY;	//检查幻数(返回值POSIX标准规定，也用-EINVAL)
	if (_IOC_NR(cmd) > G_IOC_MAXNR) return -ENOTTY;	//检查命令编号
	
	if (_IOC_DIR(cmd) & _IOC_READ)		//涉及到用户空间与内核空间数据交互，判断读OK吗？
		err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
	if (err) return -EFAULT;
	
	switch(cmd){
		case G_IOC_RESET:
			printk(KERN_INFO "ioctl reset\n");
			kfree(g_dev->c);
			g_dev->n=0;
			break;
		case G_IOCP_GET_LENS:
			printk(KERN_INFO "ioctl get lens through pointer\n");
			retval = __put_user(g_dev->n,(int __user *)arg);
			break;
		case G_IOCV_GET_LENS:
			printk(KERN_INFO "ioctl get lens through value\n");
			return g_dev->n;
			break;
		case G_IOCP_SET_LENS:
			printk(KERN_INFO "ioctl set lens through pointer");
			if (! capable (CAP_SYS_ADMIN))
				return -EPERM;			
			retval = get_user(tmp,(int __user *)arg);			
			//g_dev->n = min(g_dev->n,tmp);
			if(g_dev->n>tmp)
				g_dev->n=tmp;
			printk(KERN_INFO " %d\n",g_dev->n);
			break;
		case G_IOCV_SET_LENS:
			printk(KERN_INFO "ioctl set lens through value");
			if (! capable (CAP_SYS_ADMIN))
				return -EPERM;			
			g_dev->n = min(g_dev->n,(int)arg);
			printk(KERN_INFO " %d\n",g_dev->n);
			break;
		default:	//前面做了cmd的检查，这里可以不需要
			break;
	}
	
	return retval;
}


struct file_operations g_fops = {
	.owner =    THIS_MODULE,
	.read =     g_read,
	.write =    g_write,
	.open =     g_open,
	.release =  g_release,
	.unlocked_ioctl = g_ioctl,
	.compat_ioctl = g_ioctl,	
};

static int __init gtest_init(void)	
{
	printk(KERN_INFO "gtest module init\n");
	return 0;
}

static void __exit gtest_exit(void)
{
	printk(KERN_INFO "gtest module exit\n");
}

module_init(gtest_init);
module_exit(gtest_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Gideon");		
MODULE_VERSION("V1.0");  	
